package design_patterns_and_best_practices_in_java;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

interface Ps2Device {
    String GND = "PS/2 GND";
    String BLUE = "PS/2 BLUE";
    String BLACK = "PS/2 BLACK";
    String GREEN = "PS/2 GREEN";
    String WHITE = "PS/2 WHITE";
    String _5V = "PS/2 5V";

    List<Wire> getWires();

    void printWiresConnectionsToRight();
}

interface UsbDevice {
    void plugInto(UsbPort port);
}

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author lzy
 * @create 2019-11-14 下午2:48
 */
public class Chapter4 {
    public static void main(String[] args) {
        UsbDevice adapter = new Ps2ToUsbAdapter(new Ps2Keyboard());
        adapter.plugInto(new UsbPort());
    }
}

class UsbPort {
    public final Wire wireRed = new Wire("USB Red5V");
    public final Wire wireWhite = new Wire("USB White");
    public final Wire wireGreen = new Wire("USB Green");
    public final Wire wireBlack = new Wire("USB Black");
}

class Ps2Keyboard implements Ps2Device {

    public final List<Wire> wires = Arrays.asList(
            new Wire(_5V),
            new Wire(BLUE),
            new Wire(BLACK),
            new Wire(GREEN),
            new Wire(WHITE),
            new Wire(GND)
    );

    @Override
    public List<Wire> getWires() {
        return Collections.unmodifiableList(wires);
    }

    @Override
    public void printWiresConnectionsToRight() {
        for (Wire wire : wires) {
            wire.printWireConnectionsToRight();
        }
    }
}

class Ps2ToUsbAdapter implements UsbDevice {

    private Ps2Device ps2Device;

    public Ps2ToUsbAdapter(Ps2Device ps2Device) {
        this.ps2Device = ps2Device;
    }

    @Override
    public void plugInto(UsbPort port) {
        List<Wire> ps2Wires = ps2Device.getWires();
        Wire wireRed = getWrieWithNameFromList(Ps2Device._5V, ps2Wires);
        Wire wireWhite = getWrieWithNameFromList(Ps2Device.WHITE, ps2Wires);
        Wire wireGreen = getWrieWithNameFromList(Ps2Device.GREEN, ps2Wires);
        Wire wireBlack = getWrieWithNameFromList(Ps2Device.GND, ps2Wires);

        port.wireRed.linkLeftTo(wireRed);
        port.wireWhite.linkLeftTo(wireWhite);
        port.wireGreen.linkLeftTo(wireGreen);
        port.wireBlack.linkLeftTo(wireBlack);

        ps2Device.printWiresConnectionsToRight();
    }

    private Wire getWrieWithNameFromList(String name, List<Wire> ps2wires) {
        return ps2wires.stream().filter(x -> name.equals(x.getName())).findAny().orElse(null);
    }
}

class WireCap {
    public static WireCap looseCap = new WireCap(null);
    WireCap link = looseCap;
    private Wire wire;

    public WireCap(Wire wire) {
        this.wire = wire;
    }

    public void addLinkTo(WireCap link) {
        this.link = link;
    }

    public Wire getWire() {
        return wire;
    }

    public WireCap getLink() {
        return link;
    }

    @Override
    public String toString() {
        if (looseCap.equals(this)) {
            return "WireCap belonging to LooseCap";
        }

        return "WireCap belonging to " + wire + " is linked to " + link.getWire();
    }
}

class Wire {
    private String name;
    private WireCap left;
    private WireCap right;

    public Wire(String name) {
        this.name = name;
        this.left = new WireCap(this);
        this.right = new WireCap(this);
    }

    public void linkLeftTo(Wire link) {
        left.addLinkTo(link.getRightWireCap());
        link.getRightWireCap().addLinkTo(left);
    }

    public WireCap getRightWireCap() {
        return right;
    }

    public void printWireConnectionsToRight() {
        Wire wire = this;
        while (wire.hasLinkedRightCap()) {
            wire.printRightCap();
            wire = wire.getRightLink();
        }
    }

    public Wire getRightLink() {
        return getRightWireCap().getLink().getWire();
    }

    public void printRightCap() {
        System.out.println(getRightWireCap());
    }

    public boolean hasLinkedRightCap() {
        return !WireCap.looseCap.equals(getRightWireCap().link);
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wire " + name;
    }
}